//
//  PMSpace.m
//  PezMonkey
//
//  Created by Spence DiNicolantonio on 11/2/09.
//  Copyright 2009 Necrosoft. All rights reserved.
//

#pragma mark TODO: Comment Me

#import "PMSpace.h"
#import "PMBody.h"
#import "PMConstraint.h"
#import "PMCollision.h"

#define kDefaultSubsteps 3


#pragma mark -
#pragma mark Private Interface
@interface PMSpace (private)

// collision callbacks
+ (NSString *)collisionCallbackKeyForTypeA:(PMCollisionType)typeA typeB:(PMCollisionType)typeB;
+ (NSString *)defaultCollisionCallbackKey;
+ (NSInvocation *)collisionCallbackWithTarget:(id)target selector:(SEL)selector;
- (NSInvocation *)defaultCollisionCallback;
- (NSInvocation *)collisionCallbackForTypeA:(PMCollisionType)typeA typeB:(PMCollisionType)typeB;	

// collision handling
- (BOOL)handleCollision:(PMCollision *)collision;

// pass through
- (NSString *)passingShapesKeyForShape:(PMShape *)shape;
- (NSMutableSet *)passingShapesSetForShape:(PMShape *)shape;
- (BOOL)collisionShouldPassThrough:(PMCollision *)collision;
- (void)addShape:(PMShape *)passingShape toPassingShapesForShape:(PMShape *)shape;
- (void)removeShape:(PMShape *)passingShape fromPassingShapesForShape:(PMShape *)shape;

// post-step processing
- (void)performPostStepCalls;

@end
#pragma mark -


@implementation PMSpace

@synthesize subSteps;
@synthesize passThroughEnabled;
@synthesize data;


#pragma mark -
#pragma mark Init

/**
 * Returns a PMSpace object initialized with zero gravity.
 */
- (id)init {
	return [self initWithGravity:PMVectZero];
}
	
/**
 * Returns a PMSpace object initialized with a given gravity vector. Upon initialization,
 * the PMShape hash ID counter will be reset.
 */
- (id)initWithGravity:(PMVect)gravity {
	if (![super init])
		return nil;
	
	// allocate and initialize cpSpace
	space = cpSpaceNew();
	
	// set default steps per frame
	[self setSubSteps:kDefaultSubsteps];
	
	// set gravity vector
	[self setGravity:gravity];
	
	// initialize arrays
	bodies = [[NSMutableArray alloc] init];
	activeShapes = [[NSMutableArray alloc] init];
	staticShapes = [[NSMutableArray alloc] init];
	constraints = [[NSMutableArray alloc] init];
	collisionCallbacks = [[NSMutableDictionary alloc] init];
	postStepCallbacks = [[NSMutableArray alloc] init];
	
	// disable pass-through by default
	passThroughEnabled = NO;
	
	// initialize pass-through dictionary
	passingShapes = [[NSMutableDictionary alloc] init];
	
	// reset shape ID counter
	[PMShape resetShapeIDCounter];
	
	return self;
}


#pragma mark Convenience

/**
 * Creates and returns a PMSpace object initialized with zero gravity.
 */
+ (PMSpace *)space {
	return [[[PMSpace alloc] init] autorelease];
}

/**
 * Creates and returns a PMSpace object initialized with a given gravity vector.
 */
+ (PMSpace *)spaceWithGravity:(PMVect)gravity {
	return [[[PMSpace alloc] initWithGravity:gravity] autorelease];
}

	
#pragma mark -
#pragma mark Dealloc

/**
 * Releases member objects and frees memory used for cpSpace before deallocating.
 */
- (void)dealloc {
	[bodies release];
	[activeShapes release];
	[staticShapes release];
	[constraints release];
	[collisionCallbacks release];
	[postStepCallbacks release];
	[passingShapes release];
	cpSpaceFree(space);
	[super dealloc];
}


#pragma mark -
#pragma mark Getters

/**
 * Returns the receiver's cpSpace reference. This is not intended for external use.
 */
- (cpSpace *)cpSpace {
	return space;
}

/**
 * Returns the number of iterations used by the receiver's impulse solver for 
 * basic collisions.
 */
- (NSUInteger)iterations {
	return space->iterations;
}

/**
 * Returns the number of iterations used by the receiver's impulse solver for 
 * elastic collisions.
 */
- (NSUInteger)elasticIterations {
	return space->elasticIterations;
}
	
/**
 * Returns the receiver's gravity vector. This vector represents both the direction
 * and magnitude (in Gs) of the receiver's simulated acceleration due to gravity.
 */
- (PMVect)gravity {
	return (PMVect) space->gravity;
}

/**
 * Returns the receiver's damping coefficient.
 */
- (PMFloat)damping {
	return space->damping;
}

/**
 * Returns the number of steps that have been taken since the receiver's creation.
 */
- (NSUInteger)steps {
	return space->stamp;
}

/**
 * Returns an array of all rigid bodies in the receiver. The returned array will
 * be ordered according to the order in which bodies were added to the receiver.
 */
- (NSArray *)bodies {
	return [NSArray arrayWithArray:bodies];
}

/**
 * Returns an array of all active shapes in the receiver. The returned array will
 * be ordered according to the order in which active shapes were added to the 
 * receiver. This method simply calls the receiver's activeShapes method.
 */
- (NSArray *)shapes {
	return [self activeShapes];
}

/**
 * Returns an array of all active shapes in the receiver. The returned array will
 * be ordered according to the order in which active shapes were added to the 
 * receiver.
 */
- (NSArray *)activeShapes {
	return [NSArray arrayWithArray:activeShapes];
}

/**
 * Returns an array of all static shapes in the receiver. The returned array will
 * be ordered according to the order in which static shapes were added to the
 * receiver.
 */
- (NSArray *)staticShapes {
	return [NSArray arrayWithArray:staticShapes];
}

/**
 * Returns an array of all static and active shapes in the receiver. The returned
 * array will be ordered with all static shapes first, in the order they were added
 * to the receiver, followed by all active shapes, in the order they were added
 * to the receiver.
 */
- (NSArray *)allShapes {
	return [staticShapes arrayByAddingObjectsFromArray:activeShapes];
}

/**
 * Returns an array of all constraints in the receiver. The returned array will
 * be ordered according to the order in which constraints were added to the 
 * receiver.
 */
- (NSArray *)constraints {
	return [NSArray arrayWithArray:constraints];
}

/**
 * Returns an array of all collisions currently active in the receiver. This array 
 * is populated on the fly and not cached, so it should be avoided whenever possible.
 */
- (NSArray *)collisions {
	NSMutableArray *array = [NSMutableArray array];
	
	// populate array of collisions
	cpArray *arbiters = space->arbiters;
	for(int i=0; i<arbiters->num; i++) {
		PMCollision *currArb = [PMCollision collisionWithSpace:self 
													   arbiter:(cpArbiter *)arbiters->arr[i]
														 state:PMCollisionStateUndefined];
		[array addObject:currArb];
	}
	
	return array;
}


#pragma mark -
#pragma mark Setters

/**
 * Sets the number of iterations used by the receiver's impulse solver for basic
 * collisions to a given value.
 */
- (void)setIterations:(NSUInteger)iterations {
	space->iterations = iterations;
}

/**
 * Sets the number of iterations used by the receiver's impulse solver for elastic
 * collisions to a given value.
 */
- (void)setElasticIterations:(NSUInteger)elasticIterations {
	space->elasticIterations = elasticIterations;
}

/**
 * Sets the receiver's gravity vector to a given vector. The given vector should 
 * represents both the direction and magnitude (in Gs) of the acceleration due 
 * to gravity to be simulated by the receiver.
 */
- (void)setGravity:(PMVect)gravity {
	space->gravity = PMVectMult(gravity, PMGravityConst);
}

/**
 * Sets the receiver's damping coefficient to a given value.
 */
- (void)setDamping:(PMFloat)damping {
	space->damping = damping;
}

/**
 * Controls whether shapes in this space can pass through other shapes in special 
 * cases.
 */
- (void)setPassThroughEnabled:(BOOL)flag {
	passThroughEnabled = flag;
	
	// add default handler to backend Chipmunk engine
	if (flag)
		cpSpaceSetDefaultCollisionHandler(space, 
										  0, 0,
										  (cpCollisionBeginFunc) PMSpaceChipmunkCollisionCallbackBegin, 
										  (cpCollisionPreSolveFunc) PMSpaceChipmunkCollisionCallbackPreSolve, 
										  (cpCollisionPostSolveFunc) PMSpaceChipmunkCollisionCallbackPostSolve, 
										  (cpCollisionSeparateFunc) PMSpaceChipmunkCollisionCallbackSeparate, 
										  self);	
	// if no default callback exists, 
	// remove default handler from backend Chipmunk engine
	if (!flag && ![self defaultCollisionCallback])
		cpSpaceSetDefaultCollisionHandler(space, 0, 0, NULL, NULL, NULL, NULL, NULL);
}


#pragma mark -
#pragma mark Add Elements

/**
 * Adds a given rigid body to the receiver.
 */
- (PMBody *)addBody:(PMBody *)body {
	// already in space?
	if ([self containsBody:body])
		return body;
	
	// add body to set
	[bodies addObject:body];
	
	// add body's referenced cpBody to cpSpace
	cpSpaceAddBody(space, body.cpBody);
	
	// return the added body for convenience
	return body;
}


/**
 * Adds a given collision shape to the receiver. If the static flag is set to 
 * YES, then the given shape will be added as a static shape; otherwise, the 
 * shape will be active.
 */
- (PMShape *)addShape:(PMShape *)shape isStatic:(BOOL)isStatic {
	if (isStatic)
		return [self addStaticShape:shape];
	else
		return [self addShape:shape];
}

/**
 * Adds a given collision shape to the receiver as an active shape. If the given 
 * collision shape is already contained in the receiver as a static shape, the
 * shape is made active. This method simply calls the receiver's addActiveShape:
 * method
 */
- (PMShape *)addShape:(PMShape *)shape {
	return [self addActiveShape:shape];
}

/**
 * Adds a given collision shape to the receiver as an active shape. If the given 
 * collision shape is already contained in the receiver as a static shape, the
 * shape is made active.
 */
- (PMShape *)addActiveShape:(PMShape *)shape {
	// already in space as active?
	if ([self containsShape:shape])
		return shape;
	
	// already in space as static?
	if ([self containsStaticShape:shape])
		[self removeShape:shape];
	
	// add shape to set
	[activeShapes addObject:shape];
	
	// add shape's referenced cpShape to cpSpace
	cpSpaceAddShape(space, shape.cpShape);
	
	// return the added collision shape for convenience
	return shape;
}

/**
 * Adds a given collision shape to the receiver as a static shape. If the given 
 * collision shape is already contained in the receiver as an active shape, the
 * shape is made static.
 */
- (PMShape *)addStaticShape:(PMShape *)shape {
	// already in space as static?
	if ([self containsStaticShape:shape])
		return shape;
	
	// already in space as active?
	if ([self containsShape:shape])
		[self removeShape:shape];
	
	// add shape to set
	[staticShapes addObject:shape];
	
	// add shape's referenced cpShape to cpSpace
	cpSpaceAddStaticShape(space, shape.cpShape);
	
	// return the added collision shape for convenience
	return shape;
}

/**
 * Adds a given constraint to the receiver.
 */
- (PMConstraint *)addConstraint:(PMConstraint *)constraint {
	// already in space?
	if ([self containsConstraint:constraint])
		return constraint;
	
	// add constraint to set
	[constraints addObject:constraint];
	
	// add constraint's referenced cpConstraint to cpSpace
	cpSpaceAddConstraint(space, constraint.cpConstraint);
	
	// return the added constraint for convenience
	return constraint;
}


#pragma mark -
#pragma mark Remove Elements

/**
 * Removes a given rigid body from the receiver.
 */
- (void)removeBody:(PMBody *)body {
	// not in space?
	if (![self containsBody:body])
		return;
	
	// remove body from set
	[bodies removeObject:body];
	
	// remove body's referenced cpBody from cpSpace
	cpSpaceRemoveBody(space, body.cpBody);
}

/**
 * Removes a given collision shape from the receiver. This method will remove
 * the given shape whether or not it is static.
 */
- (void)removeShape:(PMShape *)shape {
	// active shape?
	if ([self containsShape:shape]) {
		
		// remove shape from active shape set
		[activeShapes removeObject:shape];
		
		// remove shape's referenced cpShape from cpSpace
		cpSpaceRemoveShape(space, shape.cpShape);
		
		// remove shape from all passingShapes sets
		for (PMShape *currentShape in activeShapes)
			[self removeShape:shape fromPassingShapesForShape:currentShape];
		
		// remove shape's passingShapes set
		NSString *key = [self passingShapesKeyForShape:shape];
		[passingShapes removeObjectForKey:key];
	} 
	
	// static shape?
	else if ([self containsStaticShape:shape]) {
		
		// remove shape from static shape set
		[staticShapes removeObject:shape];
		
		// remove shape's referenced cpShape from cpSpace
		cpSpaceRemoveStaticShape(space, shape.cpShape);
		
		// remove shape from all passingShapes sets
		for (PMShape *currentShape in staticShapes)
			[self removeShape:shape fromPassingShapesForShape:currentShape];
		
		// remove shape's passingShapes set
		NSString *key = [self passingShapesKeyForShape:shape];
		[passingShapes removeObjectForKey:key];
	} 
	
	// shape not found?
	else {
		NSLog(@"Shape: %@ not found in space: %@", shape, self);
	}
}

/**
 * Removes a given collision shape and its corresponding rigid body from the 
 * receiver. This method will remove the given shape whether or not it is static.
 */
- (void)removeShapeAndBody:(PMShape *)shape {
	[self removeBody:shape.body];
	[self removeShape:shape];
}

/**
 * Removes a given constraint from the receiver.
 */
- (void)removeConstraint:(PMConstraint *)constraint {
	// not in space?
	if (![self containsConstraint:constraint])
		return;
	
	// remove constraint from set
	[constraints removeObject:constraint];
	
	// remove constraint's referenced cpConstraint from cpSpace
	cpSpaceRemoveConstraint(space, constraint.cpConstraint);
}
	 
/**
 * Removes all rigid bodies from the receiver.
 */
- (void)removeAllBodies {
	for (PMBody *body in bodies)
		[self removeBody:body];
}

/**
 * Removes all collision shapes from the receiver.
 */
- (void)removeAllShapes {
	for (PMShape *shape in activeShapes)
		[self removeShape:shape];
	for (PMShape *shape in staticShapes)
		[self removeShape:shape];
}

/**
 * Removes all constraints from the receiver.
 */
- (void)removeAllConstraints {
	for (PMConstraint *constraint in constraints)
		[self removeConstraint:constraint];
}

/**
 * Removes all rigid bodies, collision shapes, and constraints from the receiver.
 */
- (void)removeAll {
	[self removeAllConstraints];
	[self removeAllShapes];
	[self removeAllBodies];
}


#pragma mark -
#pragma mark Queries

/**
 * Returns YES if the given rigid body is in the receiver; NO otherwise.
 */
- (BOOL)containsBody:(PMBody *)body {
	return [bodies containsObject:body];
}

/**
 * Returns YES if the given collision shape is an active shape in the receiver;
 * NO otherwise. This method simply calls the receiver's containsActiveShape: 
 * method and returns the result.
 */
- (BOOL)containsShape:(PMShape *)shape {
	return [self containsActiveShape:shape];
}

/**
 * Returns YES if the given collision shape is an active shape in the receiver;
 * NO otherwise.
 */
- (BOOL)containsActiveShape:(PMShape *)shape {
	return [activeShapes containsObject:shape];
}

/**
 * Returns YES if the given collision shape is a static shape in the receiver;
 * NO otherwise.
 */
- (BOOL)containsStaticShape:(PMShape *)shape {
	return [staticShapes containsObject:shape];
}

/**
 * returns YES if the given constraint is in the receiver; NO otherwise.
 */
- (BOOL)containsConstraint:(PMConstraint *)constraint {
	return [constraints containsObject:constraint];
}

/**
 * Determines whether one given shape is currently passing through another given
 * shape.
 */
- (BOOL)isShape:(PMShape *)shapeA passingThroughShape:(PMShape *)shapeB {
	NSSet *set = [self passingShapesSetForShape:shapeB];
	return [set containsObject:shapeA];
}
	
/**
 * Returns the first shape found at a given point, disregarding collision group
 * and layers. The returned shape may be active or static. If no shape is found, 
 * nil is returned.
 */
- (PMShape *)shapeAtPoint:(PMVect)point {
	return [self shapeAtPoint:point 
					   layers:PMLayerMaskAll
						group:PMCollisionGroupNone];
}

/**
 * Returns the first shape found at a given point, taking into account the shapes'
 * collision group and layers with respect to those given. The returned shape may 
 * be active or static. If no shape is found, nil is returned.
 */
- (PMShape *)shapeAtPoint:(PMVect)point layers:(PMLayerMask)layers group:(PMCollisionGroup)group {
	for (PMShape *shape in [self allShapes])
		if ([shape containsPoint:point layers:layers group:group])
			return shape;
	
	// no shape found at point
	return nil;
}

/**
 * Returns the first shape intersected by given segment, disregarding collision 
 * group and layers. The returned shape may be active or static. If no shape is 
 * found, nil is returned. The given PMSegmentCollisionInfo structure will be 
 * populated with all needed information about the collision between the given 
 * segment and the returned shape. If no shape is found the structure will be set 
 * to PMSegmentCollisionInfoNone.
 */
- (PMShape *)shapeOnSegment:(PMSegment)segment info:(PMSegmentCollisionInfo *)info {
	return [self shapeOnSegment:segment 
						 layers:PMLayerMaskAll
						  group:PMCollisionGroupNone
						   info:info];
}

/**
 * Returns the first shape intersected by a given segment, taking into account 
 * the shapes' collision group and layers with respect to those given. The returned 
 * shape may be active or static. If no shape is found, nil is returned. The given 
 * PMSegmentCollisionInfo structure will be populated with all needed information 
 * about the collision between the given segment and the returned shape. If no 
 * shape is found the structure will be set to PMSegmentCollisionInfoNone.
 */
- (PMShape *)shapeOnSegment:(PMSegment)segment layers:(PMLayerMask)layers group:(PMCollisionGroup)group info:(PMSegmentCollisionInfo *)info {
	PMShape *closestShape = nil;
	PMSegmentCollisionInfo closestInfo;
	
	// query each shape to find first intersected
	for (PMShape *shape in [self allShapes]) {
		if ([shape intersectsSegment:segment layers:layers group:group info:info]) {
			
			// first shape found, or new closest?
			if (!closestShape) {
				closestShape = shape;
				closestInfo = *info;
			} else if (info->intersect < closestInfo.intersect) {
				closestShape = shape;
				closestInfo = *info;
			}
		}
	}
	
	// set info and return shape
	info->segment = closestInfo.segment;
	info->shape = closestInfo.shape;
	info->intersect = closestInfo.intersect;
	info->normal = closestInfo.normal;
	return closestShape;
}


#pragma mark -
#pragma mark Spacial Hash

/**
 * Sets the size of the spacial hash for active collision shapes to a given value 
 * with a given cell dimension. By default, the active hash size is set to 1000, 
 * with cell dimension set to 100.0. 
 * 
 * In general, the hash size should be set to 10x the number of expected active 
 * collision shapes and the cell dimension should be set to about half of the 
 * average shape size.
 */
- (void)setActiveHashSize:(NSUInteger)size cellDimension:(PMFloat)dimension {
	cpSpaceResizeActiveHash(space, dimension, size);
}

/**
 * Sets the size of the spacial hash for static collision shapes to a given value 
 * with a given cell dimension. By default, the active hash size is set to 1000, 
 * with cell dimension set to 100.0. 
 * 
 * In general, the hash size should be set to 10x the number of expected static 
 * collision shapes and the cell dimension should be set to about half of the 
 * average shape size.
 */
- (void)setStaticHashSize:(NSUInteger)size cellDimension:(PMFloat)dimension {
	cpSpaceResizeStaticHash(space, dimension, size);
}

/**
 * Rehashes all static shapes. This method should be called whenever static shapes
 * are moved.
 */
- (void)rehashStatic {
	cpSpaceRehashStatic(space);
}


#pragma mark -
#pragma mark Collision Callbacks

/**
 * Registers a given target, selector and argument as the default callback for
 * all collisions without explicitly registered callbacks.
 */
- (void)registerDefaultCollisionCallbackTarget:(id)target selector:(SEL)selector {
	// create NSInvocation
	NSInvocation *callback = [PMSpace collisionCallbackWithTarget:target 
												selector:selector];
	// verify callback creation
	if (!callback)
		return;
	
	// retain callback arguments 
	[callback retainArguments];
	
	// get appropriate callback key
	NSString *key = [PMSpace defaultCollisionCallbackKey];
	
	// store the callback
	[collisionCallbacks setObject:callback forKey:key];
	
	// set default collision handler in backend Chipmunk engine
	// self is added as the user defined data argument to provide back reference 
	// to the collision callback list
	cpSpaceSetDefaultCollisionHandler(space, 
									  0, 0,
									  (cpCollisionBeginFunc) PMSpaceChipmunkCollisionCallbackBegin, 
									  (cpCollisionPreSolveFunc) PMSpaceChipmunkCollisionCallbackPreSolve, 
									  (cpCollisionPostSolveFunc) PMSpaceChipmunkCollisionCallbackPostSolve, 
									  (cpCollisionSeparateFunc) PMSpaceChipmunkCollisionCallbackSeparate, 
									  self);
}

/**
 * Removes the default collision callback.
 */
- (void)removeDefaultCollisionCallback {
	// remove default callback from backend Chipmunk engine
	cpSpaceSetDefaultCollisionHandler(space, 0, 0, NULL, NULL, NULL, NULL, NULL);

	// get appropriate callback key
	NSString *key = [PMSpace defaultCollisionCallbackKey];
	
	// remove callback
	[collisionCallbacks removeObjectForKey:key];
}

/**
 * Registers a given target, selector, and argument as the callback for collisions 
 * between two given collision types.
 */
- (void)registerCollisionCallbackTarget:(id)target selector:(SEL)selector forTypeA:(PMCollisionType)typeA typeB:(PMCollisionType)typeB {
	// create NSInvocation
	NSInvocation *callback = [PMSpace collisionCallbackWithTarget:target 
												selector:selector];
	// verify callback creation
	if (!callback)
		return;
	
	// retain callback arguments 
	[callback retainArguments];
	
	// get appropriate callback key
	NSString *key = [PMSpace collisionCallbackKeyForTypeA:typeA typeB:typeB];
	
	// store the callback
	[collisionCallbacks setObject:callback forKey:key];
	
	// set callback function in backend Chipmunk engine
	// self is added as the user defined data argument to allow back reference 
	// to the collision callback list
	cpSpaceAddCollisionHandler(space, 
							   typeA, typeB, 
							   (cpCollisionBeginFunc) PMSpaceChipmunkCollisionCallbackBegin, 
							   (cpCollisionPreSolveFunc) PMSpaceChipmunkCollisionCallbackPreSolve, 
							   (cpCollisionPostSolveFunc) PMSpaceChipmunkCollisionCallbackPostSolve, 
							   (cpCollisionSeparateFunc) PMSpaceChipmunkCollisionCallbackSeparate,
							   self);	
}

/**
 * Removes the registered callback for collisions between two given collision types.
 */
- (void)removeCollisionCallbackForTypeA:(PMCollisionType)typeA typeB:(PMCollisionType)typeB {
	// remove callback from backend Chipmunk engine
	cpSpaceRemoveCollisionHandler(space, typeA, typeB);
	
	// get appropriate callback key
	NSString *key = [PMSpace collisionCallbackKeyForTypeA:typeA typeB:typeB];

	// remove callback
	[collisionCallbacks removeObjectForKey:key];
}


#pragma mark Private

/**
 * Returns the dictionary key used to store the default collision callback.
 */
+ (NSString *)defaultCollisionCallbackKey {
	return @"default collision";
}

/**
 * Returns the dictionary key used to store the callback for collisions between 
 * two given collision types.
 */
+ (NSString *)collisionCallbackKeyForTypeA:(PMCollisionType)typeA typeB:(PMCollisionType)typeB {
	// typeA should be a smaller value than typeB 
	if (typeA > typeB) {
		PMCollisionType temp = typeA;
		typeA = typeB;
		typeB = temp;
	}
	
	// create and return key string
	return [NSString stringWithFormat:@"collision between %d and %d", typeA, typeB];
}

/**
 * Creates and returns an NSInvocation callback with a given target and selector.
 */
+ (NSInvocation *)collisionCallbackWithTarget:(id)target selector:(SEL)selector {
	// get method signature for given target and selector
	NSMethodSignature *signature = [[target class] instanceMethodSignatureForSelector:selector];
	
	// verify selector
	if (!signature) {
		NSLog(@"Target: \"%@\" does not respond to selector: \"%@\". Callback creation failed.",
			  target, NSStringFromSelector(selector));
		return nil;
	}
		
	// create NSInvocation
	NSInvocation *callback = [NSInvocation invocationWithMethodSignature:signature];
	[callback setTarget:target];
	[callback setSelector:selector];
	
	return callback;
}

/**
 * Returns the NSInvocation object registered as the default collision callback. 
 * Returns nil if not set.
 */
- (NSInvocation *)defaultCollisionCallback {
	// get appropriate dictionary key
	NSString *key = [PMSpace defaultCollisionCallbackKey];
	
	// return callback
	return [collisionCallbacks objectForKey:key];
}

/**
 * Returns the NSInvocation object registered as the collision callback for 
 * collisions between two given collision types. Returns nil if not set.
 */
- (NSInvocation *)collisionCallbackForTypeA:(PMCollisionType)typeA typeB:(PMCollisionType)typeB {
	// get appropriate dictionary key
	NSString *key = [PMSpace collisionCallbackKeyForTypeA:typeA typeB:typeB];
	
	// return callback
	return [collisionCallbacks objectForKey:key];
}


#pragma mark -
#pragma mark Post Step Callbacks

/**
 * Registers a given target, selector, and user-defined data argument, as a postponed
 * method call to be performed after the next space step. A message containing 
 * the given selector and argument will be sent to the given target only once.
 */
- (void)registerPostStepCallbackTarget:(id)target selector:(SEL)selector userData:(id)userData {
	// get method signature for given target and selector
	NSMethodSignature *signature = [[target class] instanceMethodSignatureForSelector:selector];
	
	// verify selector
	if (!signature) {
		NSLog(@"Target: \"%@\" does not respond to selector: \"%@\". Callback creation failed.",
			  target, NSStringFromSelector(selector));
		return;
	}
	
	// create NSInvocation
	NSInvocation *callback = [NSInvocation invocationWithMethodSignature:signature];
	[callback setTarget:target];
	[callback setSelector:selector];
	[callback setArgument:userData atIndex:2];
	
	// add NSInvocation to post step callbacks
	[postStepCallbacks addObject:callback];
}

/** 
 * Schedules a given body to be removed from the receiver after the completion 
 * of the next space step.
 */
- (void)postStepRemoveBody:(PMBody *)body {
	// define target and selector
	id target = self;
	SEL selector = @selector(removeBody:);
	
	// get method signature for remove
	NSMethodSignature *signature = [[target class] instanceMethodSignatureForSelector:selector];
	
	// create NSInvocation
	NSInvocation *callback = [NSInvocation invocationWithMethodSignature:signature];
	[callback setTarget:target];
	[callback setSelector:selector];
	[callback setArgument:&body atIndex:2];
	
	// retain arguments 
	[callback retainArguments];
	
	// add NSInvocation to post step callbacks
	[postStepCallbacks addObject:callback];
}

/** 
 * Schedules a given shape to be removed from the receiver after the completion 
 * of the next space step.
 */
- (void)postStepRemoveShape:(PMShape *)shape {
	// define target and selector
	id target = self;
	SEL selector = @selector(removeShape:);
	
	// get method signature for remove
	NSMethodSignature *signature = [[target class] instanceMethodSignatureForSelector:selector];
	
	// create NSInvocation
	NSInvocation *callback = [NSInvocation invocationWithMethodSignature:signature];
	[callback setTarget:target];
	[callback setSelector:selector];
	[callback setArgument:&shape atIndex:2];
	
	// retain arguments 
	[callback retainArguments];
	
	// add NSInvocation to post step callbacks
	[postStepCallbacks addObject:callback];
}

/** 
 * Schedules a given constraint to be removed from the receiver after the completion 
 * of the next space step.
 */
- (void)postStepRemoveConstraint:(PMConstraint *)constraint {
	// define target and selector
	id target = self;
	SEL selector = @selector(removeConstraint:);
	
	// get method signature for remove
	NSMethodSignature *signature = [[target class] instanceMethodSignatureForSelector:selector];
	
	// create NSInvocation
	NSInvocation *callback = [NSInvocation invocationWithMethodSignature:signature];
	[callback setTarget:target];
	[callback setSelector:selector];
	[callback setArgument:&constraint atIndex:2];
	
	// retain arguments 
	[callback retainArguments];
	
	// add NSInvocation to post step callbacks
	[postStepCallbacks addObject:callback];
}
	
/** 
 * Schedules a given shape and its corresponding body to be removed from the receiver
 * after the completion of the next space step.
 */
- (void)postStepRemoveShapeAndBody:(PMShape *)shape {
	// define target and selector
	id target = self;
	SEL selector = @selector(removeShapeAndBody:);
	
	// get method signature for remove
	NSMethodSignature *signature = [[target class] instanceMethodSignatureForSelector:selector];
	
	// create NSInvocation
	NSInvocation *callback = [NSInvocation invocationWithMethodSignature:signature];
	[callback setTarget:target];
	[callback setSelector:selector];
	[callback setArgument:&shape atIndex:2];
	
	// retain arguments 
	[callback retainArguments];
	
	// add NSInvocation to post step callbacks
	[postStepCallbacks addObject:callback];
}


#pragma mark Private

/**
 * Invokes and removes all registered post step callbacks.
 */
- (void)performPostStepCalls {
	// invoke each callback method
	for (NSInvocation *callback in postStepCallbacks) {
		[callback invoke];
	}
	
	// remove all callbacks
	[postStepCallbacks removeAllObjects];
}


#pragma mark -
#pragma mark Step

/**
 * Updates the receiver, by stepping through physics simulation of a given time
 * interval. The backend Chipmunk engine will be stepped stepsPerFrame times.
 */
- (void)step:(NSTimeInterval)deltaT {
	// calculate step time
	PMFloat stepTime = (deltaT / (PMFloat)subSteps);
	
	// perform sub steps
	for (int i=0; i<subSteps; i++) {
		cpSpaceStep(space, stepTime);
		
		// perform post step callbacks
		[self performPostStepCalls];
	}
}


#pragma mark -
#pragma mark Collision Handling (Private)

/** 
 * Handles a given collision. If a collision callback has been registered for 
 * collisions defining the given collision, the callback will be invoked.
 */
- (BOOL)handleCollision:(PMCollision *)collision {
	PMShape *shapeA = collision.shapeA;
	PMShape *shapeB = collision.shapeB;
	PMCollisionType typeA = [shapeA collisionType];
	PMCollisionType typeB = [shapeB collisionType];

	
	/** Pass Through **/
	
	BOOL pass = NO;
	
	// pass-through enabled?
	if (passThroughEnabled) {
		
		// check for pass through start (update passingShapes lists)
		if (collision.state == PMCollisionStateBegan)
			pass = [self collisionShouldPassThrough:collision];
		
		// determine if passing through
		if (collision.state == PMCollisionStatePreSolve)
			pass = ([self isShape:shapeA passingThroughShape:shapeB] ||
					[self isShape:shapeB passingThroughShape:shapeA]);
	}
		
	// remove shapes from passingShapes lists
	if (collision.state == PMCollisionStateEnded) {
		[self removeShape:shapeA fromPassingShapesForShape:shapeB];
		[self removeShape:shapeB fromPassingShapesForShape:shapeA];
	}
	
	
	/** Collision Callback **/
	
	// check for specific collision pair callback
	// if none, check for default callback
	NSInvocation *callback = [self collisionCallbackForTypeA:typeA typeB:typeB];
	if (!callback)
		callback = [self defaultCollisionCallback];
	
	// if no callback at all just return yes
	if (!callback)
		return !pass; 
	
	// if callback exists, add collision object and invoke
	BOOL callbackReturn = YES;
    if ([[callback methodSignature] numberOfArguments] > 2)
        [callback setArgument:&collision atIndex:2];
	[callback invoke];
	
	// store return value, if boolean
	NSMethodSignature *callbackSig = [callback methodSignature];
	if (strcmp([callbackSig methodReturnType], "c") == 0)
		[callback getReturnValue:&callbackReturn];
	
	// return collision results
	return (callbackReturn && !pass);
}


#pragma mark Pass Through

/**
 * Returns the key used to store the set of shapes currently passing through a 
 * given shape.
 */
- (NSString *)passingShapesKeyForShape:(PMShape *)shape {
	return [NSString stringWithFormat:@"Shape with hash ID: %i", shape.hashID];
}

/**
 * Returns the set of shapes currently passing through a given shape.
 */
- (NSMutableSet *)passingShapesSetForShape:(PMShape *)shape {
	NSString *key = [self passingShapesKeyForShape:shape];
	return (NSMutableSet *)[passingShapes objectForKey:key];
}

/**
 * Determines if the given collision should allow the shapes involved to pass
 * through each other, and updates each shapes passingShapes set as needed.
 */
- (BOOL)collisionShouldPassThrough:(PMCollision *)collision {
	PMShape *shapeA = collision.shapeA;
	PMShape *shapeB = collision.shapeB;
	
	// no passThrough set?
	if (PMVectEquals(shapeA.passThrough, PMVectZero) && PMVectEquals(shapeB.passThrough, PMVectZero))
		return NO;
	
	// get collision normal
	PMVect normal = [collision contact:0].normal;
	
	// determine if shape should pass through
	PMFloat dotA = PMVectDot(normal, shapeA.passThrough);
	PMFloat dotB = PMVectDot(PMVectInverse(normal), shapeB.passThrough);
	
	BOOL pass = NO;
	
	// passing through shapeA?
	if (dotB < 0) {
		[self addShape:shapeB toPassingShapesForShape:shapeA];
		pass = YES;
	}
	
	// passing through shapeB?
	if (dotA < 0) {
		[self addShape:shapeA toPassingShapesForShape:shapeB];
		pass = YES;
	}
	
	return pass;
}

/**
 * Adds a given shape to the passingShapes dictionary for another given shape.
 */
- (void)addShape:(PMShape *)passingShape toPassingShapesForShape:(PMShape *)shape {
	// check for shape set
	NSMutableSet *set = [self passingShapesSetForShape:shape];
	
	// create new set?
	if (!set) { 
		NSString *key = [self passingShapesKeyForShape:shape];
		[passingShapes setObject:[NSMutableSet setWithObject:passingShape]
						  forKey:key];
	
	} else
		// add passing shape to set
		[set addObject:passingShape];
}

/**
 * Removes a given shape from the passingShapes dictionary for another given shape.
 */
- (void)removeShape:(PMShape *)passingShape fromPassingShapesForShape:(PMShape *)shape {
	// check for shape set
	NSMutableSet *set = [self passingShapesSetForShape:shape];
	
	// remove passing shape
	[set removeObject:passingShape];
	
	// remove empty set?
	if ([set count] == 0) {
		NSString *key = [self passingShapesKeyForShape:shape];
		[passingShapes removeObjectForKey:key];
	}
}
	
@end



#pragma mark -
#pragma mark Functions

/**
 * This function is registered in the backend Chipmunk engine as the "begin" 
 * callback for collision callbacks registered in a PMSace. This function will 
 * sort out the collision information and perform the appropriate method invocation.
 */
static BOOL PMSpaceChipmunkCollisionCallbackBegin(cpArbiter *arb, struct cpSpace *sp, void *data) {
	// verify data pointer is PMSpace storing callback
	if (![(id)data isKindOfClass:PMSpace.class]) {
		NSLog(@"Chipmunk collision callback data pointer not set to PMSpace object."
			  @" Callback not invoked.");
		return YES;
	}
	
	// get space object
	PMSpace *space = (PMSpace *)data;
	
	// create PMCollision object
	PMCollision *collision = [PMCollision collisionWithSpace:space 
													 arbiter:arb 
													   state:PMCollisionStateBegan];
	// handle collision
	return [space handleCollision:collision];
}

/**
 * This function is registered in the backend Chipmunk engine as the "pre-solve" 
 * callback for collision callbacks registered in a PMSace. This function will 
 * sort out the collision information and perform the appropriate method invocation.
 */
static BOOL PMSpaceChipmunkCollisionCallbackPreSolve(cpArbiter *arb, struct cpSpace *sp, void *data) {
	// verify data pointer is PMSpace storing callback
	if (![(id)data isKindOfClass:PMSpace.class]) {
		NSLog(@"Chipmunk collision callback data pointer not set to PMSpace object."
			  @" Callback not invoked.");
		return YES;
	}
	
	// get space object
	PMSpace *space = (PMSpace *)data;
	
	// create PMCollision object
	PMCollision *collision = [PMCollision collisionWithSpace:space 
													 arbiter:arb 
													   state:PMCollisionStatePreSolve];
	// handle collision
	return [space handleCollision:collision];
}

/**
 * This function is registered in the backend Chipmunk engine as the "post-solve" 
 * callback for collision callbacks registered in a PMSace. This function will 
 * sort out the collision information and perform the appropriate method invocation.
 */
static void PMSpaceChipmunkCollisionCallbackPostSolve(cpArbiter *arb, struct cpSpace *sp, void *data) {
	// verify data pointer is PMSpace storing callback
	if (![(id)data isKindOfClass:PMSpace.class]) {
		NSLog(@"Chipmunk collision callback data pointer not set to PMSpace object."
			  @" Callback not invoked.");
		return;
	}
	
	// get space object
	PMSpace *space = (PMSpace *)data;
	
	// create PMCollision object
	PMCollision *collision = [PMCollision collisionWithSpace:space 
													 arbiter:arb 
													   state:PMCollisionStatePostSolve];
	// handle collision
	[space handleCollision:collision];	
}

/**
 * This function is registered in the backend Chipmunk engine as the "separate" 
 * callback for collision callbacks registered in a PMSace. This function will 
 * sort out the collision information and perform the appropriate method invocation.
 */
static void PMSpaceChipmunkCollisionCallbackSeparate(cpArbiter *arb, struct cpSpace *sp, void *data) {
	// verify data pointer is PMSpace storing callback
	if (![(id)data isKindOfClass:PMSpace.class]) {
		NSLog(@"Chipmunk collision callback data pointer not set to PMSpace object."
			  @" Callback not invoked.");
		return;
	}
	
	// get space object
	PMSpace *space = (PMSpace *)data;
	
	// create PMCollision object
	PMCollision *collision = [PMCollision collisionWithSpace:space 
													 arbiter:arb 
													   state:PMCollisionStateEnded];
	// handle collision
	[space handleCollision:collision];
}
